package cn.blnp.net.flowable.event.middle.compensate;

import cn.hutool.core.map.MapUtil;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.filter.SimplePropertyPreFilter;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.RuntimeService;
import org.flowable.engine.TaskService;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.Execution;
import org.flowable.engine.runtime.ExecutionQuery;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.Task;
import org.flowable.task.api.TaskQuery;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * <h3>中间事件-补偿中间抛出事件</h3>
 *
 * @author <a href="mailto:blnp.yibin@qq.com">lyb</a>
 * @version 1.0
 * @since 2025/5/9 17:06
 */
@Slf4j
@SpringBootTest
public class MidCompensateEvent {

    @Resource
    private RepositoryService repositoryService;
    @Resource
    private RuntimeService runtimeService;
    @Resource
    private TaskService taskService;

    /**
     *  <p>
     *      补偿中间抛出事件用于触发补偿，当流程流转到补偿中间抛出事件时触发该流程已完成活动的边界补偿事件，完成补偿操作后流程沿补偿中间抛出事件的外出顺序流继续流转。
     *   事件定义格式如下所示：
     *   <pre>
     *       {@code
     *          <process id="intermediateThrowEventProcess">
     *              <intermediateThrowEvent id="intermediateCompensation" name="补偿中间抛出事件">
     *                  <!-- 包含compensateEventDefinition子元素，代表补偿中间抛出事件 -->
     *                  <compensateEventDefinition />
     *              </intermediateThrowEvent>
     *          </process>
     *       }
     *   </pre>
     *  </p>
     *  <h2 style="color:red;">注意事项：</h2>
     *  <p>
     *      补偿中间抛出事件主要用于触发补偿，可以针对指定活动或包含补偿事件的作用域触发补偿，通过执行与活动相关联的补偿处理器来执行补偿。补偿遵循以下规则。
     *      <ol>
     *          <li>如果针对某项流程活动进行补偿，则相关补偿处理器执行的次数与活动成功完成的次数相等。</li>
     *          <li>如果针对当前作用域进行补偿，则对当前作用域内的所有活动进行补偿，包括并行分支上的活动。</li>
     *          <li>
     *              补偿是<b>分级触发</b>的：如果要补偿的活动是子流程，则为子流程中包含的所有活动触发补偿；如果该子流程包含嵌套的活动，则补偿事件会递归地向下抛出，
     *           但是补偿事件不会传播到比该流程高的层级；如果补偿在子流程中触发，则不会传播到该子流程作用域外的活动上。BPMN规范规定，只有“同一级别的子流程”的活动才触发补偿。
     *          </li>
     *          <li>触发补偿时，补偿的执行次序与流程执行顺序相反，这意味着最后完成的活动会最先执行补偿，以此类推</li>
     *          <li>补偿中间抛出事件可以用于补偿已经完成的事务子流程。</li>
     *          <li>当多实例活动抛出补偿时，只有当所有实例都结束了，相关补偿处理器才会执行。这意味着多实例活动在被补偿前必须先结束。</li>
     *          <li>目前暂不支持waitForCompletion="false"属性配置，当使用补偿中间抛出事件触发补偿时，事件仅在补偿成功完成后才被保留；</li>
     *          <li>补偿不会传播给调用活动创建的子流程实例。</li>
     *      </ol>
     *  </p>
     *  <br/>
     *  <h2 style="color:#000000;">案例演示：</h2>
     *  <hr/>
     *  <p>
     *      该流程为用户报名流程：“预报名”用户任务提交后，由并行网关分出两个并行分支，其中一个分支流转到在线报名内嵌子流程，另一个分支流转到“银行卡支付”用户任务。假设当前流程实例
     *   已经流转到这两个分支，并且第一个分支流转到子流程后正在等待由用户完成“报名审核”任务，另一个分支在执行“银行卡支付”用户任务处抛出“支付失败”错误，流转到“取消报名”补偿中间抛出
     *   事件抛出补偿事件。
     *  </p>
     *  <p>
     *      如果此时子流程尚未结束，意味着补偿事件不会传播给子流程，因此子流程中的“取消正式报名”服务任务（被指定为补偿处理器）不会执行；如果子流程中的“报名审核”用户任务在“取消报名”抛出
     *   事件之前完成、子流程已结束，则补偿事件会传播给子流程。“取消预报名”服务任务（被指定为补偿处理器）在以上两种情况下均会执行。<br/>
     *   <b>流程定义文件：</b>{@code flow/midCompensateEvent.bpmn20.xml}<br/>
     *   <b>流程预览图：</b><br/>
     *   <img src="https://free4.yunpng.top/2025/05/09/681dcf7d33b34.png" />
     *  </p>
     **/
    @Test
    public void test() {
        Deployment deploy = repositoryService.createDeployment()
                .name("补偿中间抛出事件演示")
                .addClasspathResource("flow/midCompensateEvent.bpmn20.xml")
                .deploy();
        log.info("部署流程定义：{}", deploy.getId());
        ProcessDefinition procDef = repositoryService.createProcessDefinitionQuery()
                .deploymentId(deploy.getId())
                .singleResult();
        log.info("流程定义编号：{}", procDef.getId());
        //启动流程实例
        ProcessInstance procInst = runtimeService.startProcessInstanceById(procDef.getId());
        TaskQuery taskQuery = taskService.createTaskQuery()
                .processInstanceId(procInst.getId());
        //查询并完成第一个任务
        Task firstTask = taskQuery.singleResult();
        log.info("第一个用户任务为：{}", firstTask.getName());
        ExecutionQuery executionQuery = runtimeService.createExecutionQuery()
                .processInstanceId(procInst.getId());
        //查询执行实例
        List<Execution> executionList1 = executionQuery.list();
        log.info("主流程发起后，执行实例数为：{}，分别为：{}", executionList1.size(),
                JSON.toJSONString(executionList1, executionFilter));
        Map<String, Object> varMap1 = MapUtil.builder(new HashMap<String, Object>())
                .put("applicant", "zhangsan")
                .build();
        //完成“预报名”用户任务
        taskService.complete(firstTask.getId(), varMap1);
        log.info("预报名申请已完成！");
        //查询执行实例
        List<Execution> executionList2 = executionQuery.list();
        log.info("子流程发起后，执行实例数为：{}，分别为：{}", executionList2.size(),
                JSON.toJSONString(executionList2, executionFilter));
        //查询“正式报名”用户任务
        Task secondTask = taskQuery.taskName("正式报名").singleResult();
        //完成“正式报名”用户任务
        taskService.complete(secondTask.getId());
        log.info("办理完成名称为：{}的用户任务", secondTask.getName());
        //查询“报名审核”用户任务
        Task thirdTask = taskQuery.taskName("报名审核").singleResult();
        //完成“报名审核”用户任务
        taskService.complete(thirdTask.getId());
        log.info("办理完成名称为：{}的用户任务", thirdTask.getName());
        //查询执行实例
        List<Execution> executionList3 = executionQuery.list();
        log.info("子流程结束后，执行实例数为：{}，分别为：{}", executionList3.size(),
                JSON.toJSONString(executionList3, executionFilter));
        //查询“银行卡支付”任务
        Task fourthTask = taskQuery.taskName("银行卡支付").singleResult();
        log.info("即将办理名称为：{}的用户任务", fourthTask.getName());
        //设置流程变量
        Map<String, Object> varMap2 = MapUtil.builder(new HashMap<String, Object>())
                .put("applicationFee", 1000)
                        .build();
        //完成第二个任务（流程结束）
        taskService.complete(fourthTask.getId(), varMap2);
        //查询执行实例
        List<Execution> executionList4 = executionQuery.list();
        log.info("流程结束后，执行实例数为：{}，执行实例信息为：{}", executionList4.size(),
                JSON.toJSONString(executionList4, executionFilter));
        /**
         *  演示结果：
         * 2025-05-09 18:05:15.168  INFO 65760 --- [           main] c.b.n.f.e.m.c.MidCompensateEvent         : 部署流程定义：1a2c7e98-2cbd-11f0-a143-50ebf6ec8f6d
         * 2025-05-09 18:05:15.172  INFO 65760 --- [           main] c.b.n.f.e.m.c.MidCompensateEvent         : 流程定义编号：1a5deedb-2cbd-11f0-a143-50ebf6ec8f6d
         * 2025-05-09 18:05:15.225  INFO 65760 --- [           main] c.b.n.f.e.m.c.MidCompensateEvent         : 第一个用户任务为：预报名
         * 2025-05-09 18:05:15.383  INFO 65760 --- [           main] c.b.n.f.e.m.c.MidCompensateEvent         : 主流程发起后，执行实例数为：2，分别为：[{"id":"1a6541dc-2cbd-11f0-a143-50ebf6ec8f6d","processInstanceId":"1a6541dc-2cbd-11f0-a143-50ebf6ec8f6d","rootProcessInstanceId":"1a6541dc-2cbd-11f0-a143-50ebf6ec8f6d","scope":true},{"activityId":"firstUserTaskOfMainProcess","id":"1a6568ed-2cbd-11f0-a143-50ebf6ec8f6d","parentId":"1a6541dc-2cbd-11f0-a143-50ebf6ec8f6d","processInstanceId":"1a6541dc-2cbd-11f0-a143-50ebf6ec8f6d","rootProcessInstanceId":"1a6541dc-2cbd-11f0-a143-50ebf6ec8f6d","scope":false}]
         * 2025-05-09 18:05:15.452  INFO 65760 --- [           main] c.b.n.f.e.m.c.MidCompensateEvent         : 预报名申请已完成！
         * 2025-05-09 18:05:15.456  INFO 65760 --- [           main] c.b.n.f.e.m.c.MidCompensateEvent         : 子流程发起后，执行实例数为：5，分别为：[{"id":"1a6541dc-2cbd-11f0-a143-50ebf6ec8f6d","processInstanceId":"1a6541dc-2cbd-11f0-a143-50ebf6ec8f6d","rootProcessInstanceId":"1a6541dc-2cbd-11f0-a143-50ebf6ec8f6d","scope":true},{"activityId":"secondUserTaskOfMainProcess","id":"1a88f687-2cbd-11f0-a143-50ebf6ec8f6d","parentId":"1a6541dc-2cbd-11f0-a143-50ebf6ec8f6d","processInstanceId":"1a6541dc-2cbd-11f0-a143-50ebf6ec8f6d","rootProcessInstanceId":"1a6541dc-2cbd-11f0-a143-50ebf6ec8f6d","scope":false},{"activityId":"signUpSubProcess","id":"1a88f68a-2cbd-11f0-a143-50ebf6ec8f6d","parentId":"1a6541dc-2cbd-11f0-a143-50ebf6ec8f6d","processInstanceId":"1a6541dc-2cbd-11f0-a143-50ebf6ec8f6d","rootProcessInstanceId":"1a6541dc-2cbd-11f0-a143-50ebf6ec8f6d","scope":true},{"activityId":"firstUserTaskOfSubProcess","id":"1a88f68c-2cbd-11f0-a143-50ebf6ec8f6d","parentId":"1a88f68a-2cbd-11f0-a143-50ebf6ec8f6d","processInstanceId":"1a6541dc-2cbd-11f0-a143-50ebf6ec8f6d","rootProcessInstanceId":"1a6541dc-2cbd-11f0-a143-50ebf6ec8f6d","scope":false},{"activityId":"firstErrorBoundaryEventOfMainProcess","id":"1a88f68e-2cbd-11f0-a143-50ebf6ec8f6d","parentId":"1a88f687-2cbd-11f0-a143-50ebf6ec8f6d","processInstanceId":"1a6541dc-2cbd-11f0-a143-50ebf6ec8f6d","rootProcessInstanceId":"1a6541dc-2cbd-11f0-a143-50ebf6ec8f6d","scope":false}]
         * 2025-05-09 18:05:15.479  INFO 65760 --- [           main] c.b.n.f.e.m.c.MidCompensateEvent         : 办理完成名称为：正式报名的用户任务
         * 2025-05-09 18:05:15.518  INFO 65760 --- [           main] c.b.n.f.e.m.c.MidCompensateEvent         : 办理完成名称为：报名审核的用户任务
         * 2025-05-09 18:05:15.521  INFO 65760 --- [           main] c.b.n.f.e.m.c.MidCompensateEvent         : 子流程结束后，执行实例数为：4，分别为：[{"id":"1a6541dc-2cbd-11f0-a143-50ebf6ec8f6d","processInstanceId":"1a6541dc-2cbd-11f0-a143-50ebf6ec8f6d","rootProcessInstanceId":"1a6541dc-2cbd-11f0-a143-50ebf6ec8f6d","scope":true},{"activityId":"secondUserTaskOfMainProcess","id":"1a88f687-2cbd-11f0-a143-50ebf6ec8f6d","parentId":"1a6541dc-2cbd-11f0-a143-50ebf6ec8f6d","processInstanceId":"1a6541dc-2cbd-11f0-a143-50ebf6ec8f6d","rootProcessInstanceId":"1a6541dc-2cbd-11f0-a143-50ebf6ec8f6d","scope":false},{"activityId":"firstErrorBoundaryEventOfMainProcess","id":"1a88f68e-2cbd-11f0-a143-50ebf6ec8f6d","parentId":"1a88f687-2cbd-11f0-a143-50ebf6ec8f6d","processInstanceId":"1a6541dc-2cbd-11f0-a143-50ebf6ec8f6d","rootProcessInstanceId":"1a6541dc-2cbd-11f0-a143-50ebf6ec8f6d","scope":false},{"activityId":"signUpSubProcess","id":"1a95eeed-2cbd-11f0-a143-50ebf6ec8f6d","parentId":"1a6541dc-2cbd-11f0-a143-50ebf6ec8f6d","processInstanceId":"1a6541dc-2cbd-11f0-a143-50ebf6ec8f6d","rootProcessInstanceId":"1a6541dc-2cbd-11f0-a143-50ebf6ec8f6d","scope":false}]
         * 2025-05-09 18:05:15.522  INFO 65760 --- [           main] c.b.n.f.e.m.c.MidCompensateEvent         : 即将办理名称为：银行卡支付的用户任务
         * 2025-05-09 18:05:15.527 ERROR 65760 --- [           main] c.b.n.f.e.m.compensate.PaymentListener   : 余额不足，支付失败！
         * 2025-05-09 18:05:15.543  INFO 65760 --- [           main] c.b.n.f.e.m.c.CancelSignUpService        : 执行补偿，取消正式报名完成！
         * 2025-05-09 18:05:15.544  INFO 65760 --- [           main] c.b.n.f.e.m.c.CancelPredictionService    : 执行补偿，取消预报名完成！
         * 2025-05-09 18:05:15.603  INFO 65760 --- [           main] c.b.n.f.e.m.c.MidCompensateEvent         : 流程结束后，执行实例数为：0，执行实例信息为：[]
         **/
    }

    /**
     *  字段属性过滤器
     **/
    SimplePropertyPreFilter executionFilter = new SimplePropertyPreFilter(Execution.class,
            "id", "parentId", "businessKey", "processInstanceId", "superExecutionId",
            "rootProcessInstanceId", "scope", "activityId");
}
